/*!
    \copyright  Copyright (c) 2023-2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    le_unicast_manager
    \brief      Utilities for LE unicast manager
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST)

#include "device.h"
#include "device_types.h"
#include "device_list.h"
#include "device_properties.h"
#include "source_param_types.h"
#include "bt_device.h"
#include "le_unicast_manager_utils.h"
#include "le_unicast_manager_instance.h"
#include "le_unicast_voice_source.h"
#include "le_unicast_music_source.h"

typedef struct
{
    /*! The audio source associated with the device to find */
    generic_source_t source;

    /*! Set to TRUE if a device with the source is found */
    bool source_found;
} le_um_source_search_data_t;

static void leUnicastManager_SearchForDeviceWithLeaSource(device_t device, void * data)
{
    le_um_source_search_data_t *search_data = (le_um_source_search_data_t *) data;
    bool matches = (search_data->source.type == source_type_audio &&
                    DeviceProperties_GetLeAudioSource(device) == search_data->source.u.audio) ||
                   (search_data->source.type == source_type_voice &&
                    DeviceProperties_GetLeVoiceSource(device) == search_data->source.u.voice);
    if (matches)
    {
        deviceType device_type = BtDevice_GetDeviceType(device);
        if (device_type == DEVICE_TYPE_HANDSET || device_type == DEVICE_TYPE_SINK)
        {
            search_data->source_found = TRUE;
        }
    }
}

/*! \brief Clear the LE audio or LE voice source from device if it has allocated any */
static void leUnicastManager_ClearDeviceWithLeaSource(device_t device, void * data)
{
    UNUSED(data);

    LeUnicastManager_RemoveSourceFromDevice(device, source_type_audio);
    LeUnicastManager_RemoveSourceFromDevice(device, source_type_voice);
}

void LeUnicastManager_RemoveSourceFromDevice(device_t device, source_type_t source_type)
{
    if (device != NULL)
    {
        UNICAST_MANAGER_LOG("LeUnicastManager_RemoveSourceFromDevice enum:source_type_t:%d, device:%p", source_type, device);

        if (source_type == source_type_audio)
        {
            DeviceProperties_SetLeAudioSource(device, audio_source_none);
        }
        else
        {
            DeviceProperties_SetLeVoiceSource(device, voice_source_none);
        }
    }
}

void LeUnicastManager_AllocateSourceToDevice(device_t device, source_type_t source_type)
{
    if (device != NULL)
    {
        le_um_source_search_data_t search_data;

        search_data.source_found= FALSE;
        search_data.source.type = source_type;
        if (source_type == source_type_audio)
        {
            search_data.source.u.audio = audio_source_le_audio_unicast_1;
        }
        else
        {
            search_data.source.u.voice = voice_source_le_audio_unicast_1;
        }

        /* Find a free audio source */
        DeviceList_Iterate(leUnicastManager_SearchForDeviceWithLeaSource, &search_data);
        if (search_data.source_found)
        {
            /* If audio/voice_source_le_audio_unicast_1 has been allocated, try to allocate audio/voice_source_le_audio_unicast_2 */
            search_data.source_found = FALSE;
            if (source_type == source_type_audio)
            {
                search_data.source.u.audio = audio_source_le_audio_unicast_2;
            }
            else
            {
                search_data.source.u.voice = voice_source_le_audio_unicast_2;
            }

            DeviceList_Iterate(leUnicastManager_SearchForDeviceWithLeaSource, &search_data);
        }

        if (!search_data.source_found)
        {
            /* A free audio_source exists, allocate it to the handset device. */
            if (source_type == source_type_audio)
            {
                DeviceProperties_SetLeAudioSource(device, search_data.source.u.audio);
            }
            else
            {
                 DeviceProperties_SetLeVoiceSource(device, search_data.source.u.voice);
            }

            DEBUG_LOG_VERBOSE("LeUnicastManager_AllocateSourceToDevice source_type: enum:source_type_t:%d, source: %d, device:%p",
                              source_type, search_data.source.u.audio, device);
        }
        else
        {
            /* It should be impossible to have connected the BAP profile if we have already
               two connected audio sources for BAP, this may indicate a handle was leaked. */
            Panic();
        }
    }
    else
    {
        DEBUG_LOG_VERBOSE("LeUnicastManager_AllocateSourceToDevice couldn't allocate the source to device");
    }
}

bool leUnicastManager_IsAnyCisConnected(le_um_instance_t *inst)
{
    le_um_cis_t *cis;

    ARRAY_FOREACH(cis, inst->cis)
    {
        if (cis->cis_handle != LE_INVALID_CIS_HANDLE)
        {
            return TRUE;
        }
    }

    return FALSE;
}

/*!< Check if any change ase1 is routed and ase2 is not in ready to be routed state */
static bool leUnicastManager_IsAnyChangeInBetweenAseRouteState(le_um_ase_t *ase1, le_um_ase_t *ase2)
{
    bool reconfig = FALSE;

    if ((ase1->state == le_um_ase_state_routed || ase1->state == le_um_ase_state_streaming) &&
        leUnicastManager_IsAseActive(ase2) &&
        ase2->cis_data != NULL &&
        LeUnicastManager_IsCisEstablished(ase2->cis_data->state))
    {
        reconfig = TRUE;
    }

    return reconfig;
}

static bool leUnicastManager_IsReconfigRequired(le_um_instance_t *inst, multidevice_side_t side)
{
    bool is_reconfig_needed = FALSE;
    le_um_ase_t *sink_ase = NULL;
    le_um_ase_t *source_ase = NULL;

    leUnicastManager_GetAseFromSide(inst, side, &sink_ase, &source_ase);

    if(leUnicastManager_IsAnyChangeInBetweenAseRouteState(source_ase, sink_ase) ||
       leUnicastManager_IsAnyChangeInBetweenAseRouteState(sink_ase, source_ase))
    {
        is_reconfig_needed = TRUE;
    }

    DEBUG_LOG_INFO("leUnicastManager_IsReconfigRequired side: %d, is_reconfig_needed: %d", side, is_reconfig_needed);

    return is_reconfig_needed;
}

bool leUnicastManager_CheckAndReconfigAudio(le_um_instance_t *inst)
{
    if (leUnicastManager_IsReconfigRequired(inst, Multidevice_GetSide()) ||
        leUnicastManager_IsReconfigRequired(inst, Multidevice_GetPairSide()))
    {
        bool is_cis_and_data_path_ready = LeUnicastManager_IsAllCisConnected(inst, TRUE);

        UNICAST_MANAGER_LOG("leUnicastManager_CheckAndReconfigAudio: reconfig_required, cis_status: %d", is_cis_and_data_path_ready);

        if (is_cis_and_data_path_ready)
        {
            /* Need to reconfigure the graph as new CIS is established for an ASE enable */
            if (LeUnicastManager_IsContextTypeConversational(inst->audio_context))
            {
                LeUnicastVoiceSource_Reconfig(inst);
            }
            else
            {
                LeUnicastMusicSource_Reconfig(inst);
            }
        }
    }

    return TRUE;
}

void leUnicastManager_SendUnicastEnabledInd(gatt_cid_t cid, uint16 audio_context, bool is_foreground)
{
    generic_source_t source = {0};
    le_um_instance_t *inst = LeUnicastManager_InstanceGetByCid(cid);

    PanicNull(inst);
    leUnicastManager_GetGenericSourceForInstance(inst, &source);
    LeAudioMessages_SendUnicastEnabled(cid, Multidevice_GetSide(), audio_context, &source, is_foreground);
}

void leUnicastManager_SendUnicastDisabledInd(le_um_instance_t *inst)
{
    generic_source_t source = {0};

    leUnicastManager_GetGenericSourceForInstance(inst, &source);
    LeAudioMessages_SendUnicastDisabled(inst->cid, &source, inst->audio_context);
}

void leUnicastManager_SendUnicastUpdateMetadataInd(le_um_instance_t *inst, uint16 metadata_audio_context)
{
    generic_source_t source = {0};

    leUnicastManager_GetGenericSourceForInstance(inst, &source);
    LeAudioMessages_SendMetadataUpdateInd(inst->cid, &source, metadata_audio_context);
}

void LeUnicastManager_RemoveAllLeAudioVoiceSources(void)
{
    DEBUG_LOG("LeUnicastManager_RemoveAllLeAudioVoiceSources");

    /* Iterate through all devices and clear the LE audio or LE voice sources from it (if allocated)*/
    DeviceList_Iterate(leUnicastManager_ClearDeviceWithLeaSource, NULL);
}

#endif /* defined(INCLUDE_LE_AUDIO_UNICAST) */
